
package com.trydofor.id.mess;

import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author : Shi Rongjiu (www.trydofor.com)
 * @link http://trydofor.javaeye.com/blog/961492
 */
public class ThinkingInSync {
    
    private final ReadWriteLock       rwLock       = new ReentrantReadWriteLock();
    private final Lock                rLock        = rwLock.readLock();
    private final Lock                wLock        = rwLock.writeLock();
    
    // sync
    private final LinkedList<Integer> readWriteBox = new LinkedList<Integer>();
    private volatile long             unusedAmount = 0;
    private volatile Integer          currentValue = null;
    
    //private final AtomicLong          unusedAmount = new AtomicLong(); // 1
    //private long                      unusedAmount  = 0; // 2
    
    //
    private final Random              random       = new Random();
    private final AtomicInteger       counter      = new AtomicInteger(0);
    
    public Integer nextA1() throws InterruptedException, TimeoutException {
        wLock.lock();
        try {
            if (readWriteBox.size() <= 0) {
                syncLoad();
                unusedAmount = readWriteBox.size();
            }
            currentValue = makeValue();
            unusedAmount--;
        }
        finally {
            wLock.unlock();
        }
        return currentValue;
    }
    
    public Integer nextA2() throws InterruptedException, TimeoutException {
        wLock.lock();
        try {
            if (readWriteBox.size() <= 0) {
                syncLoad();
                unusedAmount = readWriteBox.size();
            }
            currentValue = makeValue();
            unusedAmount--;
            return currentValue;
        }
        finally {
            wLock.unlock();
        }
    }
    
    public Integer nextB1() throws InterruptedException, TimeoutException {
        wLock.lock();
        try {
            if (readWriteBox.size() <= 0) {
                syncLoad();
                unusedAmount = readWriteBox.size();
            }
            unusedAmount--;
            currentValue = makeValue();
            return currentValue;
        }
        finally {
            wLock.unlock();
        }
    }
    
    public Integer nextB2() throws InterruptedException, TimeoutException {
        wLock.lock();
        Integer result = null;
        long varUnused = unusedAmount;
        try {
            if (readWriteBox.size() <= 0) {
                syncLoad();
                varUnused = readWriteBox.size();
            }
            result = makeValue();
            varUnused--;
        }
        finally {
            unusedAmount = varUnused;
            currentValue = result;
            wLock.unlock();
        }
        
        return result;
    }
    
    public Integer current() {
        rLock.lock();
        try {
            return currentValue;
        }
        finally {
            rLock.unlock();
        }
    }
    
    public long remains() {
        rLock.lock();
        try {
            return unusedAmount;
        }
        finally {
            rLock.unlock();
        }
    }
    
    //-------------------------------
    private Integer makeValue() throws InterruptedException, TimeoutException {
        randomEvent();
        return readWriteBox.isEmpty() ? null : readWriteBox.removeFirst();
    }
    
    private int syncLoad() throws InterruptedException, TimeoutException {
        int sleep = randomEvent();
        readWriteBox.add(counter.incrementAndGet());
        int count = 1;
        if (sleep % 2 == 0) {
            count++;
            readWriteBox.add(counter.incrementAndGet());
        }
        if (sleep % 3 == 0) {
            count++;
            readWriteBox.add(counter.incrementAndGet());
        }
        
        return count;
    }
    
    private int randomEvent() throws InterruptedException, TimeoutException {
        int sleep = random.nextInt();
        
        if (sleep > 10000) {
            Thread.sleep(5000);
            throw new TimeoutException();
        }
        else if (sleep < 100) {
            Thread.currentThread().interrupt();
            throw new InterruptedException();
        }
        else {
            Thread.sleep(sleep);
        }
        
        return sleep;
    }
}
